using System.Collections.Generic;
using org.neo4j.kernel.impl.util;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.batchinsert
{


	using Direction = org.neo4j.graphdb.Direction;
	using GraphDatabaseService = org.neo4j.graphdb.GraphDatabaseService;
	using Node = org.neo4j.graphdb.Node;
	using NotFoundException = org.neo4j.graphdb.NotFoundException;
	using NotInTransactionException = org.neo4j.graphdb.NotInTransactionException;
	using Relationship = org.neo4j.graphdb.Relationship;
	using RelationshipType = org.neo4j.graphdb.RelationshipType;
	using ReturnableEvaluator = org.neo4j.graphdb.ReturnableEvaluator;
	using StopEvaluator = org.neo4j.graphdb.StopEvaluator;
	using Transaction = org.neo4j.graphdb.Transaction;
	using Traverser = org.neo4j.graphdb.Traverser;
	using Order = org.neo4j.graphdb.Traverser.Order;
	using LruCache = org.neo4j.kernel.impl.cache.LruCache;
	using InvalidRecordException = org.neo4j.kernel.impl.nioneo.store.InvalidRecordException;

	internal class BatchGraphDatabaseImpl : GraphDatabaseService
	{
		internal readonly BatchInserterImpl batchInserter;

		private final LruCache<long, NodeBatchImpl> nodes = new LruCache<long, NodeBatchImpl>("NodeCache", 10000, null)
			{
				public void elementCleaned(NodeBatchImpl node)
				{
					Map<string, object> properties = node.getProperties();
					if (properties != null)
					{
						batchInserter.setNodeProperties(node.getId(), properties);
					}
				}
			}

		private final LruCache<long, RelationshipBatchImpl> rels = new LruCache<long, RelationshipBatchImpl>("RelCache", 10000, null)
			{
				public void elementCleaned(RelationshipBatchImpl rel)
				{
					Map<string, object> properties = rel.getProperties();
					if (properties != null)
					{
						batchInserter.setRelationshipProperties(rel.getId(), properties);
					}
				}
			}

		BatchGraphDatabaseImpl(BatchInserterImpl batchInserter)
		{
			this.batchInserter = batchInserter;
		}

		BatchInserterImpl getBatchInserter()
		{
			return batchInserter;
		}

		public Transaction beginTx()
		{
			return new FakeTransaction();
		}

		public Node createNode()
		{
			long id = batchInserter.createNode(null);
			NodeBatchImpl node = new NodeBatchImpl(id, this, emptyProps());
			nodes.put(id, node);
			return node;
		}

		static Map<string, object> emptyProps()
		{
			return new Dictionary<string, object>();
		}

		public bool enableRemoteShell()
		{
			return false;
		}

		public bool enableRemoteShell(Map<string, Serializable> initialProperties)
		{
			return false;
		}

		public IEnumerable <Node> getAllNodes()
		{
			throw new UnsupportedOperationException("Batch inserter mode");
		}

		public Node getNodeById(long id)
		{
			NodeBatchImpl node = nodes.get(id);
			if (node == null)
			{
				try
				{
					node = new NodeBatchImpl(id, this, batchInserter.getNodeProperties(id));
					nodes.put(id, node);
				}
				catch (InvalidRecordException e)
				{
					throw new NotFoundException(e);
				}
			}
			return node;
		}

		public Node getReferenceNode()
		{
			return getNodeById(0);
		}

		public Relationship getRelationshipById(long id)
		{
			RelationshipBatchImpl rel = rels.get(id);
			if (rel == null)
			{
				try
				{
					SimpleRelationship simpleRel = batchInserter.getRelationshipById(id);
					Map<string, object> props = batchInserter.getRelationshipProperties(id);
					rel = new RelationshipBatchImpl(simpleRel, this, props);
					rels.put(id, rel);
				}
				catch (InvalidRecordException e)
				{
					throw new NotFoundException(e);
				}
			}
			return rel;
		}

		public IEnumerable <RelationshipType> getRelationshipTypes()
		{
			throw new UnsupportedOperationException("Batch inserter mode");
		}

		public void shutdown()
		{
			batchInserter.shutdown();
		}

		static class FakeTransaction : Transaction
		{
			public void failure()
			{
				throw new NotInTransactionException("Batch insert mode, " + "failure is not an option.");
			}

			public void finish()
			{
			}

			public void success()
			{
			}
		}

		private static class NodeBatchImpl implements Node
		{
			private final BatchGraphDatabaseImpl graphDbService;

			private final long id;
			private final Map<string, object> properties;

			NodeBatchImpl(long id, BatchGraphDatabaseImpl graphDbService, Map<string, object> properties)
			{
				this.id = id;
				this.graphDbService = graphDbService;
				this.properties = properties;
			}

			public Relationship createRelationshipTo(Node otherNode, RelationshipType type)
			{
				long relId = graphDbService.getBatchInserter().createRelationship(id, otherNode.getId(), type, null);
				RelationshipBatchImpl rel = new RelationshipBatchImpl(new SimpleRelationship((int)relId, (int) id, (int) otherNode.getId(), type), graphDbService, emptyProps());
				graphDbService.addRelationshipToCache(relId, rel);
				return rel;
			}

			Map<string, object> getProperties()
			{
				return properties;
			}

			public void delete()
			{
				throw new UnsupportedOperationException();
			}

			public long getId()
			{
				return id;
			}

			private RelIterator newRelIterator(Direction dir, RelationshipType[] types)
			{
				IEnumerable <long> relIds = graphDbService.getBatchInserter().getRelationshipIds(id);
				return new RelIterator(graphDbService, relIds, id, dir, types);
			}

			public IEnumerable <Relationship> getRelationships()
			{
				return newRelIterator(Direction.BOTH, null);
			}

			public IEnumerable <Relationship> getRelationships(RelationshipType... types)
			{
				return newRelIterator(Direction.BOTH, types);
			}

			public IEnumerable <Relationship> getRelationships(Direction dir)
			{
				return newRelIterator(dir, null);
			}

			public IEnumerable <Relationship> getRelationships(RelationshipType type, Direction dir)
			{
				return newRelIterator(dir, new RelationshipType[] { type });
			}

			public Relationship getSingleRelationship(RelationshipType type, Direction dir)
			{
				IEnumerator <Relationship> relItr = newRelIterator(dir, new RelationshipType[] { type });
				if (relItr.MoveNext())
				{
					Relationship rel = relItr.Current;
					if (relItr.MoveNext())
					{
						throw new NotFoundException("More than one relationship[" + type + ", " + dir + "] found for " + this);
					}
					return rel;
				}
				return null;
			}

			public bool hasRelationship()
			{
				IEnumerator <Relationship> relItr = newRelIterator(Direction.BOTH, null);
				return relItr.MoveNext();
			}

			public bool hasRelationship(RelationshipType... types)
			{
				IEnumerator <Relationship> relItr = newRelIterator(Direction.BOTH, types);
				return relItr.MoveNext();
			}

			public bool hasRelationship(Direction dir)
			{
				IEnumerator <Relationship> relItr = newRelIterator(dir, null);
				return relItr.MoveNext();
			}

			public bool hasRelationship(RelationshipType type, Direction dir)
			{
				IEnumerator <Relationship> relItr = newRelIterator(dir, new RelationshipType[] { type });
				return relItr.MoveNext();
			}

			public Traverser traverse(Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, RelationshipType relationshipType, Direction direction)
			{
				throw new UnsupportedOperationException("Batch inserter mode");
			}

			public Traverser traverse(Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, RelationshipType firstRelationshipType, Direction firstDirection, RelationshipType secondRelationshipType, Direction secondDirection)
			{
				throw new UnsupportedOperationException("Batch inserter mode");
			}

			public Traverser traverse(Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, object... relationshipTypesAndDirections)
			{
				throw new UnsupportedOperationException("Batch inserter mode");
			}

			public object getProperty(string key)
			{
				object val = properties.get(key);
				if (val == null)
				{
					throw new NotFoundException(key);
				}
				return val;
			}

			public object getProperty(string key, object defaultValue)
			{
				object val = properties.get(key);
				if (val == null)
				{
					return defaultValue;
				}
				return val;
			}

			public IEnumerable <string> getPropertyKeys()
			{
				return properties.Keys;
			}

			public IEnumerable <object> getPropertyValues()
			{
				return properties.values();
			}

			public bool hasProperty(string key)
			{
				return properties.containsKey(key);
			}

			public object removeProperty(string key)
			{
				object val = properties.Remove(key);
				if (val == null)
				{
					throw new NotFoundException("Property " + key);
				}
				return val;
			}

			public void setProperty(string key, object value)
			{
				properties.put(key, value);
			}

			public bool Equals(object o)
			{
				if (!(o is Node))
				{
					return false;
				}
				return this.getId() == ((Node) o).getId();

			}

			public int GetHashCode()
			{
				return (int) id;
			}
		}

		private static class RelationshipBatchImpl implements Relationship
		{
			private final SimpleRelationship rel;
			private final BatchGraphDatabaseImpl graphDbService;
			private final Map<string, object> properties;


			RelationshipBatchImpl(SimpleRelationship rel, BatchGraphDatabaseImpl graphDbService, Map<string, object> properties)
			{
				this.rel = rel;
				this.graphDbService = graphDbService;
				this.properties = properties;
			}

			Map<string, object> getProperties()
			{
				return properties;
			}

			public void delete()
			{
				throw new UnsupportedOperationException("Batch inserter mode");
			}

			public Node getEndNode()
			{
				return graphDbService.getNodeById(rel.getEndNode());
			}

			public long getId()
			{
				return rel.getId();
			}

			public Node[] getNodes()
			{
				return new Node[] { getStartNode(), getEndNode() };
			}

			public Node getOtherNode(Node node)
			{
				Node startNode = getStartNode();
				Node endNode = getEndNode();
				if (node.Equals(endNode))
				{
					return startNode;
				}
				if (node.Equals(startNode))
				{
					return endNode;
				}
				throw new IllegalArgumentException("" + node);
			}

			public Node getStartNode()
			{
				return graphDbService.getNodeById(rel.getStartNode());
			}

			public RelationshipType getType()
			{
				return rel.getType();
			}

			public bool isType(RelationshipType type)
			{
				return rel.getType().Equals(type);
			}

			public object getProperty(string key)
			{
				object val = properties.get(key);
				if (val == null)
				{
					throw new NotFoundException(key);
				}
				return val;
			}

			public object getProperty(string key, object defaultValue)
			{
				object val = properties.get(key);
				if (val == null)
				{
					return defaultValue;
				}
				return val;
			}

			public IEnumerable <string> getPropertyKeys()
			{
				return properties.Keys;
			}

			public IEnumerable <object> getPropertyValues()
			{
				return properties.values();
			}

			public bool hasProperty(string key)
			{
				return properties.containsKey(key);
			}

			public object removeProperty(string key)
			{
				object val = properties.Remove(key);
				if (val == null)
				{
					throw new NotFoundException("Property " + key);
				}
				return val;
			}

			public void setProperty(string key, object value)
			{
				properties.put(key, value);
			}

			public bool Equals(object o)
			{
				if (!(o is Relationship))
				{
					return false;
				}
				return this.getId() == ((Relationship) o).getId();

			}

			public int GetHashCode()
			{
				return (int) rel.getId();
			}
		}

		void addRelationshipToCache(long id, RelationshipBatchImpl rel)
		{
			rels.put(id, rel);
		}

		static class RelIterator implements IEnumerable <Relationship>, IEnumerator <Relationship>
		{

			private final BatchGraphDatabaseImpl graphDbService;
			private final IEnumerable <long> relIds;
			private final IEnumerator <long> relItr;
			private final long nodeId;
			private final Direction dir;
			private final RelationshipType[] types;

			private Relationship nextElement;

			RelIterator(BatchGraphDatabaseImpl graphDbService, IEnumerable <long> relIds, long nodeId, Direction dir, RelationshipType[] types)
			{
				this.graphDbService = graphDbService;
				this.relIds = relIds;
				this.relItr = relIds.GetEnumerator();
				this.nodeId = nodeId;
				this.dir = dir;
				this.types = types;
			}

			public IEnumerator <Relationship> iterator()
			{
				return new RelIterator(graphDbService, relIds, nodeId, dir, types);
			}

			public bool hasNext()
			{
				getNextElement();
				if (nextElement != null)
				{
					return true;
				}
				return false;
			}

			public Relationship next()
			{
				getNextElement();
				if (nextElement != null)
				{
					Relationship returnVal = nextElement;
					nextElement = null;
					return returnVal;
				}
				throw new NoSuchElementException();
			}

			private void getNextElement()
			{
				while (nextElement == null && relItr.MoveNext())
				{
					Relationship possibleRel = graphDbService.getRelationshipById(relItr.Current);
					if (dir == Direction.OUTGOING && possibleRel.getEndNode().getId() == nodeId)
					{
						continue;
					}
					if (dir == Direction.INCOMING && possibleRel.getStartNode().getId() == nodeId)
					{
						continue;
					}
					if (types != null)
					{
						foreach (RelationshipType type in types)
						{
							if (type.name().Equals(possibleRel.getType().name()))
							{
								nextElement = possibleRel;
								break;
							}
						}
					}
					else
					{
						nextElement = possibleRel;
					}
				}
			}

			public void Remove()
			{
				throw new UnsupportedOperationException();
			}
		}

		void clearCaches()
		{
			nodes.Clear();
			rels.Clear();
		}
	}
}